#! /usr/bin/python2
# -*- coding: utf-8; -*-
#
# dput/dcut.py
# Part of ‘dput’, a Debian package upload toolkit.
#
# Copyright © 2015 Ben Finney <ben+python@benfinney.id.au>
# Copyright © 2004–2008 Thomas Viehmann <tv@beamnet.de>
# Copyright © 2000–2004 Christian Kurz
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

""" dcut — Debian command upload tool. """

import sys
import os
import tempfile
import string
import time
import subprocess
import pwd

from .helper import dputhelper
from . import dput


files_to_remove = []

progname = "dcut"
version = "0.2.1"

USAGE = """Usage: %s [options] [host] command [, command]
 Supported options (see man page for long forms):
   -c file Config file to parse.
   -d      Enable debug messages.
   -h      Display this help message.
   -s      Simulate the commands file creation only.
   -v      Display version information.
   -m maintaineraddress
           Use maintainer information in "Uploader:" field.
   -k keyid
           Use this keyid for signing.
   -O file Write commands to file.
   -U file Upload specified commands file (presently no checks).
   -i changes
           Upload a commands file to remove files listed in .changes.
 Supported commands: mv, rm
   (No paths or command-line options allowed on ftp-master.)
""" % (sys.argv[0])

validcommands = ('rm', 'cancel', 'reschedule')


def getoptions():
    # seed some defaults
    options = {
            'debug': 0, 'simulate': 0, 'config': None, 'host': None,
            'uploader': None, 'keyid': None, 'passive': 0,
            'filetocreate': None, 'filetoupload': None, 'changes': None}
    # enable debugging very early
    if ('-d' in sys.argv[1:] or '--debug' in sys.argv[1:]):
        options['debug'] = 1
        print "D: %s %s" % (progname, version)

    # check environment for maintainer
    if options['debug']:
        print "D: trying to get maintainer email from environment"

    if 'DEBEMAIL' in os.environ:
        if os.environ['DEBEMAIL'].find('<') < 0:
            options['uploader'] = os.environ.get("DEBFULLNAME", '')
            if options['uploader']:
                options['uploader'] += ' '
            options['uploader'] += '<%s>' % (os.environ['DEBEMAIL'])
        else:
            options['uploader'] = os.environ['DEBEMAIL']
        if options['debug']:
            print "D: Uploader from env: %s" % (options['uploader'])
    elif 'EMAIL' in os.environ:
        if os.environ['EMAIL'].find('<') < 0:
            options['uploader'] = os.environ.get("DEBFULLNAME", '')
            if options['uploader']:
                options['uploader'] += ' '
            options['uploader'] += '<%s>' % (os.environ['EMAIL'])
        else:
            options['uploader'] = os.environ['EMAIL']
        if options['debug']:
            print "D: Uploader from env: %s" % (options['uploader'])
    else:
        if options['debug']:
            print "D: Guessing uploader"
        pwrec = pwd.getpwuid(os.getuid())
        try:
            s = open('/etc/mailname').read().strip()
        except IOError:
            s = ''
        if not s:
            if options['debug']:
                print "D: Guessing uploader: /etc/mailname was a failure"
            s = os.popen('/bin/hostname --fqdn').read().strip()
        if s:
            options['uploader'] = (
                    '%s <%s@%s>' % (pwrec[4].split(',')[0], pwrec[0], s))
            if options['debug']:
                print "D: Guessed uploader: %s" % (options['uploader'])
        else:
            if options['debug']:
                print "D: Couldn't guess uploader"
    # parse command line arguments
    (opts, arguments) = dputhelper.getopt(
            sys.argv[1:],
            'c:dDhsvm:k:PU:O:i:', [
                'config=', 'debug',
                'help', 'simulate', 'version', 'host=',
                'maintainteraddress=', 'keyid=',
                'passive', 'upload=', 'output=', 'input='
                ])

    for (option, arg) in opts:
        if options['debug']:
            print 'D: processing arg "%s", option "%s"' % (option, arg)
        if option in ('-h', '--help'):
            print USAGE
            sys.exit(0)
        elif option in ('-v', '--version'):
            print progname, version
            sys.exit(0)
        elif option in ('-d', '--debug'):
            options['debug'] = 1
        elif option in ('-c', '--config'):
            options['config'] = arg
        elif option in ('-m', '--maintaineraddress'):
            options['uploader'] = arg
        elif option in ('-k', '--keyid'):
            options['keyid'] = arg
        elif option in ('-s', '--simulate'):
            options['simulate'] = 1
        elif option in ('-P', '--passive'):
            options['passive'] = 1
        elif option in ('-U', '--upload'):
            options['filetoupload'] = arg
        elif option in ('-O', '--output'):
            options['filetocreate'] = arg
        elif option == '--host':
            options['host'] = arg
        elif option in ('-i', '--input'):
            options['changes'] = arg
        else:
            sys.stderr.write(
                    "%s internal error: Option %s, argument %s unknown\n"
                    % (progname, option, arg))
            sys.exit(1)

    if not options['host'] and arguments and arguments[0] not in validcommands:
        options['host'] = arguments[0]
        if options['debug']:
            print 'D: first argument "%s" treated as host' % (options['host'])
        del arguments[0]

    # we don't create command files without uploader
    if (
            not options['uploader']
            and (options['filetoupload'] or options['changes'])):
        sys.stderr.write(
                "%s error: command file cannot be created"
                " without maintainer email\n"
                % progname)
        sys.stderr.write(
                '%s        please set $DEBEMAIL, $EMAIL'
                ' or use the "-m" option\n'
                % (len(progname) * ' '))
        sys.exit(1)

    return options, arguments


def parse_queuecommands(arguments, options, config):
    commands = []
    # want to consume a copy of arguments
    arguments = arguments[:]
    arguments.append(0)
    curarg = []
    while arguments:
        if arguments[0] in validcommands:
            curarg = [arguments[0]]
            if arguments[0] == 'rm':
                if len(arguments) > 1 and arguments[1] == '--nosearchdirs':
                    del arguments[1]
                else:
                    curarg.append('--searchdirs')
        else:
            if not curarg and arguments[0] != 0:
                sys.stderr.write(
                        'Error: Could not parse commands at "%s"\n'
                        % (arguments[0]))
                sys.exit(1)
            if str(arguments[0])[-1] in (',', ';', 0):
                curarg.append(arguments[0][0:-1])
                arguments[0] = ','
            if arguments[0] in (',', ';', 0) and curarg:
                # TV-TODO: syntax check for #args etc.
                if options['debug']:
                    print (
                            'D: Successfully parsed command "%s"'
                            % (' '.join(curarg)))
                commands.append(' '.join(curarg))
                curarg = []
            else:
                # TV-TODO: maybe syntax check the arguments here
                curarg.append(arguments[0])
        del arguments[0]
    if not commands:
        sys.stderr.write("Error: no arguments given, see dcut -h\n")
        sys.exit(1)
    return commands


def write_commands(commands, options, config, tempdir):
    if options['filetocreate']:
        filename = options['filetocreate']
    else:
        translationorig = (
                ''.join(map(chr, range(256)))
                + string.ascii_letters + string.digits)
        translationdest = 256 * '_' + string.ascii_letters + string.digits
        uploadpartforname = string.translate(
                options['uploader'],
                string.maketrans(translationorig, translationdest))
        filename = (
                progname + '.%s.%d.%d.commands' %
                (uploadpartforname, int(time.time()), os.getpid()))
        if tempdir:
            filename = os.path.join(tempdir, filename)
            files_to_remove.append(filename)
    f = open(filename, "w")
    f.write("Uploader: %s\n" % options['uploader'])
    f.write("Commands:\n %s\n\n" % ('\n '.join(commands)))
    f.close()
    debsign_cmdline = ['debsign']
    debsign_cmdline.append('-m%s' % options['uploader'])
    if options['keyid']:
        debsign_cmdline.append('-k%s' % options['keyid'])
    debsign_cmdline.append('%s' % filename)
    if options['debug']:
        print "D: calling debsign:", debsign_cmdline
    debsign_prog = subprocess.Popen(debsign_cmdline)
    if os.waitpid(debsign_prog.pid, 0)[1]:
        sys.stderr.write("Error: debsign failed.\n")
        sys.exit(1)
    return filename


def upload_stolen_from_dput_main(
        host, upload_methods, config, debug, simulate,
        files_to_upload, ftp_passive_mode):
    # Check the upload methods that we have as default and per host
    if debug:
        print "D: Default Method: %s" % config.get('DEFAULT', 'method')
    if config.get('DEFAULT', 'method') not in upload_methods:
        sys.stderr.write(
                "Unknown upload method: %s\n"
                % config.get('DEFAULT', 'method'))
        sys.exit(1)
    if debug:
        print "D: Host Method: %s" % config.get(host, 'method')
    if config.get(host, 'method') not in upload_methods:
        sys.stderr.write(
                "Unknown upload method: %s\n" % config.get(host, 'method'))
        sys.exit(1)

    # Inspect the Config and set appropriate upload method
    if not config.get(host, 'method'):
        method = config.get('DEFAULT', 'method')
    else:
        method = config.get(host, 'method')

    # Check now the login and redefine it if needed
    if (
            config.has_option(host, 'login')
            and config.get(host, 'login') != 'username'):
        login = config.get(host, 'login')
    elif (
            config.has_option('DEFAULT', 'login')
            and config.get('DEFAULT', 'login') != 'username'):
        login = config.get('DEFAULT', 'login')
    else:
        # Try to get the login from the enviroment
        if 'USER' in os.environ:
            login = os.environ['USER']
        else:
            print "$USER not set, will use login information."
            # Else use the current username
            login = pwd.getpwuid(os.getuid())[0]
            if debug:
                print "D: User-ID: %s" % os.getuid()
        if debug:
            print (
                    "D: Neither host %s nor default login used. Using %s"
                    % (host, login))
    if debug:
        print "D: Login to use: %s" % login

    # Messy, yes. But it isn't referenced by the upload method anyway.
    if config.get(host, 'method') == 'local':
        fqdn = 'localhost'
    else:
        fqdn = config.get(host, 'fqdn')
    incoming = config.get(host, 'incoming')
    # Do the actual upload
    if not simulate:
        if debug:
            print "D: FQDN: %s" % fqdn
            print "D: Login: %s" % login
            print "D: Incoming: %s" % incoming
        if method == 'ftp':
            ftp_mode = config.getboolean(host, 'passive_ftp')
            if ftp_passive_mode == 1:
                ftp_mode = 1
            if ftp_mode == 1:
                if debug:
                    if ftp_passive_mode == 1:
                        print "D: Using passive ftp"
                    else:
                        print "D: Using active ftp"
            upload_methods[method](
                    fqdn, login, incoming,
                    files_to_upload, debug, ftp_mode)
        elif method == 'scp':
            if debug and config.getboolean(host, 'scp_compress'):
                print "D: Setting compression for scp"
            scp_compress = config.getboolean(host, 'scp_compress')
            ssh_config_options = [
                    y for y in (
                        x.strip() for x in
                        config.get(host, 'ssh_config_options').split('\n'))
                    if y]
            upload_methods[method](
                    fqdn, login, incoming,
                    files_to_upload, debug, scp_compress, ssh_config_options)
        else:
            upload_methods[method](
                    fqdn, login, incoming,
                    files_to_upload, debug, 0)
    # Or just simulate it.
    else:
        for file in files_to_upload:
            sys.stderr.write(
                    "Uploading with %s: %s to %s:%s\n"
                    % (method, file, fqdn, incoming))
            os.system("cat %s" % file)


def dcut():
    options, arguments = getoptions()
    # dput read_configs sets dput.config
    if options['debug']:
        print 'D: calling dput.read_configs'
    dput.read_configs(options['config'], options['debug'])
    config = dput.config
    if (
            not options['host']
            and config.has_option('DEFAULT', 'default_host_main')):
        options['host'] = config.get('DEFAULT', 'default_host_main')
        if options['debug']:
            print 'D: Using host "%s" (default_host_main)' % (options['host'])
        if not options['host']:
            options['host'] = 'ftp-master'
            if options['debug']:
                print 'D: Using host "%s" (hardcoded)' % (options['host'])
    tempdir = None
    filename = None
    try:
        if not (options['filetoupload'] or options['filetocreate']):
            tempdir = tempfile.mkdtemp(prefix=progname + '.')
        if not options['filetocreate']:
            if not options['host']:
                print "Error: No host specified and no default found in config"
                sys.exit(1)
            if not config.has_section(options['host']):
                print "No host %s found in config" % (options['host'])
                sys.exit(1)
            else:
                if config.has_option(options['host'], 'allow_dcut'):
                    dcut_allowed = config.getboolean(
                            options['host'], 'allow_dcut')
                else:
                    dcut_allowed = config.getboolean('DEFAULT', 'allow_dcut')
                if not dcut_allowed:
                    print 'Error: dcut is not supported for this upload queue.'
                    sys.exit(1)
        if options['filetoupload']:
            if arguments:
                print (
                        'Error: cannot take commands'
                        ' when uploading existing file,')
                print '       "%s" found' % (' '.join(arguments))
                sys.exit(1)
            commands = None
            filename = options['filetoupload']
            if not filename.endswith(".commands"):
                print 'Error: I\'m insisting on the .commands extension, which'
                print '       "%s" doesnt seem to have.' % filename
            # TV-TODO: check file to be readable?
        elif options['changes']:
            parse_changes = dput.parse_changes
            removecommands = create_commands(options, config, parse_changes)
            filename = write_commands(removecommands, options, config, tempdir)
        else:
            commands = parse_queuecommands(arguments, options, config)
            filename = write_commands(commands, options, config, tempdir)
        if not options['filetocreate']:
            dput.import_upload_functions()
            upload_methods = dput.upload_methods
            upload_stolen_from_dput_main(
                    options['host'], upload_methods, config,
                    options['debug'], options['simulate'],
                    [filename], options['passive'])
    finally:
        # we use sys.exit, so we need to clean up here
        if tempdir:
            # file is temporary iff in tempdir
            for filename in files_to_remove:
                os.unlink(filename)
            os.rmdir(tempdir)


def create_commands(options, config, parse_changes):
    """ Get the removal commands from a package changes file.

        Parse the specified ‘foo.changes’ file and returns commands to
        remove files named in it.

        """
    changes_file = options['changes']
    if options['debug']:
        print "D: Parsing changes file (%s) for files to remove" % changes_file
    try:
        chg_fd = open(changes_file, 'r')
    except IOError:
        print "Can't open changes file: %s" % changes_file
        sys.exit(1)
    the_changes = parse_changes(chg_fd)
    chg_fd.close
    removecommands = ['rm --searchdirs ' + os.path.basename(changes_file)]
    for file in the_changes.dict['files'].split('\n'):
        # filename only
        fn = string.split(file)[4]
        rm = 'rm --searchdirs ' + fn
        if options['debug']:
            print "D: Will remove %s with '%s'" % (fn, rm)
        removecommands.append(rm)
    return removecommands


if __name__ == "__main__":
    try:
        dcut()
    except dputhelper.DputException as e:
        sys.stderr.write("%s\n" % e)
        sys.exit(1)


# Local variables:
# coding: utf-8
# mode: python
# End:
# vim: fileencoding=utf-8 filetype=python :
